#import "SFUEventSearchEngine.h"

// Singleton instance of SFUEventSearchEngine
static SFUEventSearchEngine* sharedSearchEngine = nil;
// Temporary but complete collection of SFU events
static SFUEventCollection* cachedEvents;
// Temporary buffered collection of SFU events
static SFUEventCollection* bufferedEvents;
// The operation queue for caching events
static NSOperationQueue* eventCacheQueue;         

/**
 * An SFUEventSearchEngine is an object modelling a search engine
 * for all relevant events at SFU.
 * 
 * Contains the static list of parsable websites with updated
 * information on events at SFU. The SFUEventSearchEngine retains
 * a cached collection of these SFU events for matching with 
 * various search parameters.
 *
 * Results of the event search engine are handled by the event search
 * engine delegate. Thus the delegate must be set prior to any search
 * queries performed.
 *
 * Conforms to the SFUConnectionDelegate protocol.
 *
 * @author      James Lin
 * @version     1.2.0
 * @since       0.4.0
 * @see         SFUConnectionDelegate
 */
@implementation SFUEventSearchEngine

@synthesize searchKeywords;

/**
 * Retrieve the shared instance of the SFU event search engine.
 *
 * The event cache is empty upon initialization of this shared instance.
 *
 * @return  the shared instance of SFUEventSearchEngine
 */
+ (SFUEventSearchEngine*)sharedSearchEngine {
	@synchronized(self) {
		if (sharedSearchEngine == nil) {
			sharedSearchEngine = [[SFUEventSearchEngine alloc] init];
			cachedEvents = [[SFUEventCollection alloc] init];
			bufferedEvents = [[SFUEventCollection alloc] init];
			eventCacheQueue = [[NSOperationQueue alloc] init];
		}
		return sharedSearchEngine;
	}
}

/**
 * Allocate the SFU event search engine.
 *
 * @return  the newly allocated shared instance of SFUEventSearchEngine
 */
+ (id) alloc {
	@synchronized(self) {
		sharedSearchEngine = [super alloc];
		return sharedSearchEngine;
	}
}

/**
 * Retrieve a copy of the SFU event search engine.
 *
 * @return  the shared instance of SFUEventSearchEngine
 */
+ (id) copy {
	@synchronized(self) {
		return sharedSearchEngine;
	}
}

/**
 * Get the delegate handling search events.
 *
 * @return  the delegate handling search events
 * @see     SFUEventSearchEngineDelegate
 */
- (id<SFUEventSearchEngineDelegate>)delegate {
	return delegate;
}

/**
 * Set the delegate handling search events.
 *
 * @param theDelegate  the delegate handling search events
 * @see                SFUEventSearchEngineDelegate
 */
- (void)setDelegate:(id<SFUEventSearchEngineDelegate>)theDelegate {
	delegate = theDelegate;
}

/**
 * Search for all SFU events with their headings or categories 
 * containing any or all of the keywords in the given list of 
 * search keywords.
 *
 * Searching by any keyword in the list will return events sorted by
 * non-increasing order of number of keywords matched.
 *
 * An empty keyword list is valid and will search for all SFU events.
 * Enabling use of the cached events will result in an attempt to
 * search the the precached events. If the cache is empty, a full 
 * parse from website sources will be done prior to search.
 *
 * The results of the search will be passed to the delegate handling
 * search events. Thus this delegate must be set prior to the search 
 * query.
 *
 * @param keywords  the list of search keywords
 * @param flag      whether to use the event cache for the search
 * @param matchAll  YES if all keywords in the keyword list must match;
 *                  NO if any keyword can match.
 */
- (void)findEventsWithKeywords:(NSArray*)keywords 
					  useCache:(BOOL)flag 
					  matchAll:(BOOL)matchAll {
	// Create a search progress popup view
	progressView = [[[UIAlertView alloc] initWithTitle:@"\nSearching\nPlease Wait..." 
											   message:@"" 
											  delegate:self 
									 cancelButtonTitle:nil 
									 otherButtonTitles:nil] autorelease];
	
	// Create a progress indicator inside the progress popup
	progressIndicator = [[UIActivityIndicatorView alloc] 
						 initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
	progressIndicator.center = CGPointMake(140, 120);
	[progressView addSubview:progressIndicator];
	
	// Display the progress popup
	[progressIndicator startAnimating];
	[progressView show];
	
	// Cache the search keywords and matching option
	matchAllKeywords = matchAll;
	self.searchKeywords = keywords;
	searchRequested = NO;
	
	@synchronized (cachedEvents) {
		if (!flag || [cachedEvents size] == 0) { // Initiate online search
			//[delegate searchDidFinishWithResults:[cachedEvents findEventsWithKeywords:self.searchKeywords 
			//											  matchAllKeywords:matchAllKeywords]];
			searchRequested = YES;
		} else { // Offline search through cache
			if ([searchKeywords count] > 0) { 
				// Search cached events via keyword
				[delegate searchDidFinishWithResults:[cachedEvents findEventsWithKeywords:searchKeywords 
																		 matchAllKeywords:matchAllKeywords]];
			} else { 
				// Retrieve all cached events
				[delegate searchDidFinishWithResults:[cachedEvents getEvents]];
			}
			if (progressIndicator) {
				[progressView dismissWithClickedButtonIndex:0 animated:YES];
				[progressIndicator release];
			}
		}
	}
}

/**
 * Search for all SFU events with their headings or categories 
 * containing any or all of the keywords in the given list of 
 * search keywords.
 *
 * An empty keyword list is valid and will search for all SFU events.
 * Does use cached events by default.
 *
 * The results of the search will be passed to the delegate handling
 * search events. Thus this delegate must be set prior to the search 
 * query.
 *
 * @param keywords  the list of search keywords
 * @param matchAll  YES if all keywords in the keyword list must match;
 *                  NO if any keyword can match.
 */
- (void)findEventsWithKeywords:(NSArray*)keywords 
			  matchAllKeywords:(BOOL)matchAll {
	[self findEventsWithKeywords:keywords 
						useCache:YES 
						matchAll:matchAll];
}

/**
 * Search for all SFU events with their headings or categories containing 
 * any or all of the keywords in the given space-delimited keyword string.
 *
 * @precondition    keyword string is not nil
 * @param keyword   the case-insensitive search keyword string
 * @param matchAll  YES if all keywords in the keyword list must match;
 *                  NO if any keyword can match.
 */
- (void)findEventsWithKeywordString:(NSString*)keyword
				   matchAllKeywords:(BOOL)matchAll {
	[self findEventsWithKeywords:[keyword componentsSeparatedByString:@" "]
				matchAllKeywords:matchAll];
}

/**
 * Execute an update of the event cache.
 */
- (void)cacheEvents {
	// Reset the cache
	if (bufferedEvents) {
		[bufferedEvents clear];
	}
	
	// Set initial website connection count
	webSourceCount = [[SFUConstants getCalendarURLs] count];
	cacheCount = 0;
	
	// Set progress tracking variables
	expectedSize = 0.0;
	currentSize = 0.0;
	
	// Prepare the operation queue
	[eventCacheQueue cancelAllOperations];
	eventCacheQueue = [NSOperationQueue new];
	[eventCacheQueue setMaxConcurrentOperationCount:MAX_CONCURRENT_OPERATION];
	
	// Connect to the SFU calendar websites and retrieve source code
	for (NSString* url in [[SFUConstants getCalendarURLs] allValues]) {
		SFUCalendarSource* calendar = [[SFUCalendarSource alloc] 
									   initWithURLString:url 
									   delegate:self];
		WebCacheOperation* cacheOp = [[WebCacheOperation alloc] initWithWebParsable:calendar];
		[eventCacheQueue addOperation:cacheOp];
		[cacheOp release];
	}
}

/**
 * Handle a failed connection event to an SFU website.
 *
 * @param message  the message for the connection that just failed
 * @see            SFUConnectionDelegate
 */
- (void)connectionDidFail:(NSString*)message {
	webSourceCount--;
	
	// Keep track of number of website connnections
	if (webSourceCount == 0) {
		[delegate searchDidFailWithError:@"Search failed.\nUnable to connect to online sources."];
	}
}

/**
 * Add a single event to the buffer.
 *
 * Write the buffered events to cache if completely buffered.
 *
 * @param event  the event to buffer
 */
- (void)cacheEvent:(SFUEvent*)event {
	// Add the event to buffer
	@synchronized (bufferedEvents) {
		[bufferedEvents addEvent:event];
	}
	
	@synchronized (cacheCount) {
		cacheCount--;
	}
	
	// Check if events completely buffered
	@synchronized (cachedEvents) {
		if (cacheCount == 0) {
			[cachedEvents clear];
			[cachedEvents addEvents:[bufferedEvents getEvents]];
			
			// If there was a search request during the caching process, return results
			if (searchRequested) {
				[delegate searchDidFinishWithResults:[cachedEvents findEventsWithKeywords:searchKeywords 
																		 matchAllKeywords:matchAllKeywords]];
				if (progressIndicator) {
					[progressView dismissWithClickedButtonIndex:0 animated:YES];
					[progressIndicator release];
				}
				searchRequested = NO;
			}
		}
	}
}

/**
 * Handle a finished connection event to an SFU website.
 *
 * @param message  the message for the connection that just finished
 * @param results  an object of the results (can be nil)
 * @see            SFUConnectionDelegate
 */
- (void)connectionDidFinish:(NSString*)message 
					results:(id)results {
	NSArray* events = (NSArray*)results;
	webSourceCount--;
	
	// Create an operation for each event to cache
	for (SFUEvent* event in events) {
		NSInvocationOperation* saveOp = [[NSInvocationOperation alloc] initWithTarget:self 
																			 selector:@selector(cacheEvent:) 
																			   object:event];
		[eventCacheQueue addOperation:saveOp];
		[saveOp release];
		
		@synchronized (cacheCount) {
			cacheCount++;
		}
	}
}

/**
 * Deallocate the SFU event search engine.
 */
- (void)dealloc {
	delegate = nil;
	[searchKeywords release];
	searchKeywords = nil;
	[eventCacheQueue release];
	eventCacheQueue = nil;
	[cachedEvents release];
	cachedEvents = nil;
	[sharedSearchEngine release];
	sharedSearchEngine = nil;
	[super dealloc];
}
	
@end
